#define WWWROOT "./wwwroot/"

#include "util.hpp"
#include "db.hpp"
#include "room.hpp"
#include "online.hpp"
#include "session.hpp"
#include "matcher.hpp"

// 服务器要包含前面实现的所有实现好的模块句柄
class gobang_server
{
public:
    gobang_server(const std::string &host, const std::string &user, const std::string &passwd, const std::string &dbname, const uint16_t &port = 8080, const std::string &wwwroot = WWWROOT)
        : _web_root(wwwroot), _ut(host, user, passwd, dbname, port), _rm(&_ut, &_om), _mm(&_ut, &_om, &_rm), _sm(&_wssvr)
    {
        // 1. 设置websocketpp库的⽇志级别
        // all表⽰打印全部级别⽇志
        // none表⽰什么⽇志都不打印
        // wssvr.set_access_channels(websocketpp::log::alevel::all);
        _wssvr.set_access_channels(websocketpp::log::alevel::none);

        // 2. 初始化asio异步IO调度器
        _wssvr.init_asio();

        // 3. 设置地址复用(之前tcp套接字bind函数重启不能立即绑定原来的端口号)
        _wssvr.set_reuse_addr(true);

        // 4. 设置WebSocket服务器的事件处理回调函数
        //  websocketpp中的标准函数声明:
        //  -> void set_http_handler(http_handler h);  typedef lib::function<void(connection_hdl)> http_handler;
        // 补充：bind的用法有两种，一种是绑死参数，调用bind生成的可调用对象时，无论传什么参数都没用，因为bind已经绑死参数了
        //      另一种是预留参数位置，等待bind生成的可调用对象在被调用时进行实际传参!
        // -> bind不影响传递绑定对象作为参数传递时的类型，只影响调用时的实际传参
        // -> bind是function的适配器，只要bind生成的可调用对象，可以作为参数传递给其他API的任意类型的function对象参数!(---这就是适配器模式)
        _wssvr.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _wssvr.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));
        _wssvr.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));
        _wssvr.set_message_handler(std::bind(&gobang_server::wsmessage_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

    ~gobang_server()
    {
    }

    // 启动服务器
    void run(const uint16_t &port)
    {
        // 1. 设置服务器绑定的监听端口号，监听端口号上是否有连接到来
        _wssvr.listen(port);

        // 2. accept获取内核监听队列中已经完成三次握手的连接，开始通信
        _wssvr.start_accept();

        // 3. 启动运行websocket服务器
        _wssvr.run();
    }

private:
    // 处理客户端发来的静态页面请求
    void static_handler(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取请求uri(请求资源路径)，得到客户端请求的页面文件的名称
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        // 2. 组合出文件是实际路径
        std::string realPath = WWWROOT + uri; // uri可能为xxx.html or / or 空

        // 3. 如果请求的是整个目录-> / ，则在后面添加一个默认后缀名login.html
        if (realPath.back() == '/')
            realPath += "register.html";

        // 4. 读取文件内容(如果客户端请求的文件不存在，则服务器返回404页面)
        std::string body;
        bool ret = file_util::read(realPath, body);
        if (ret == false)
        {
            body += "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>404</title></head><body><h1>Not Found</h1></body></html>";
            conn->set_status(websocketpp::http::status_code::not_found);
            conn->set_body(body);
            return;
        }

        // 5. 设置响应正文
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(body);
    }

    // 处理客户端的注册请求
    void register_handler(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取用户请求正文
        std::string req_body = conn->get_request_body();

        // 2. 反序列化，拿到用户名和密码
        Json::Value reg_info;
        bool ret = json_util::deserialize(req_body, reg_info);
        if (ret == false)
        {
            DLOG("反序列化登录信息失败!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "请求正文格式错误, 请重新输入!", conn);
            return;
        }

        // 3. 判断用户名/密码是否为空
        if (reg_info["username"].isNull() || reg_info["password"].isNull())
        {
            DLOG("用户名/密码不完整!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "请输入用户名/密码!", conn);
            return;
        }

        // 4. 通过数据管理模块句柄进行用户添加
        ret = _ut.insert(reg_info);
        if (ret == false)
        {
            DLOG("用户名已存在, 向数据库添加用户失败!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "用户名已被占用, 请重新输入!", conn);
            return;
        }

        // 5. 构建相应(响应成功状态码是200，不成功是400)
        respHttp_JsonFormat(false, websocketpp::http::status_code::ok, "用户注册成功!", conn);
    }

    // 处理客户端登录请求
    void login_handler(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取用户的请求正文
        std::string req_body = conn->get_request_body();

        // 2. 反序列化，拿到用户名密码
        Json::Value req_info;
        bool ret = json_util::deserialize(req_body, req_info);
        if (ret == false)
        {
            DLOG("反序列化登录信息失败!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "请求正文格式错误, 请重新输入!", conn);
            return;
        }

        // 3. 判断用户名密码是否为空(检验完整性，看看用户有没有输入完整)
        if (req_info["username"].isNull() || req_info["password"].isNull())
        {
            DLOG("用户名/密码不完整!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "请输入用户名/密码!", conn);
            return;
        }

        // 4. 若请求正文完整，判断用户名密码是否正确，进行数据库信息验证(若验证失败，返回400状态码)
        ret = _ut.login(req_info);
        if (ret == false)
        {
            DLOG("用户名/密码错误!");
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "用户名/密码错误, 请重新输入!", conn);
            return;
        }

        // 5. 若验证成功，查看是否已经创建会话，若会话存在，则刷新销毁时间(其实这一步可以不用写，如果用户重复登陆，那每次都给他新建会话就好了)

        // 6. 如果没有会话则创建会话
        int uid = req_info["id"].asInt();
        session_ptr sp = _sm.create_session(uid, LOGIN);
        if (sp.get() == nullptr)
        {
            DLOG("创建会话失败!");
            // 服务器内部错误，响应码返回500
            respHttp_JsonFormat(false, websocketpp::http::status_code::internal_server_error, "创建会话失败!", conn);
            return;
        }

        // 7. 会话创建成功，则要刷新会话销毁时间
        _sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);
        DLOG("sp->ssid(): %d", sp->ssid());

        // 8. 创建会话成功后，构建响应(将ssid通过响应的头部字段Set-Cookie返回)返回给客户端
        std::string cookie_ssid = "SSID=" + std::to_string(sp->ssid());
        DLOG("cookie_ssid: %s", cookie_ssid.c_str());
        conn->append_header("Set-Cookie", cookie_ssid);
        respHttp_JsonFormat(true, websocketpp::http::status_code::ok, "登录成功!", conn);
        return;
    }

    // 处理客户端请求自己的数据展示在前端页面上
    void info_handler(wsserver_t::connection_ptr &conn)
    {
        // 用户信息获取功能请求的处理
        // 1. 获取Cookies信息
        std::string cookie_str = conn->get_request_header("Cookie"); // 根据请求头的字段获取对应的值
        if (cookie_str.size() == 0)
        {
            // 找不到Cookie信息
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "找不到Cookie信息, 请重新登录!", conn);
            return;
        }

        std::string ssid_str; // cookie中kv里的v，作为输出型参数
        bool ret = get_val_from_cookie_by_key(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "找不到ssid信息, 请重新登录!", conn);
            return;
        }

        // 2. 判断cookie信息是否有效
        DLOG("stoi: ssid_str: %s", ssid_str.c_str());
        session_ptr sp = _sm.get_session_by_ssid(std::stoi(ssid_str)); // std::atoi(ssid_str.c_str())
        if (sp.get() == nullptr)
        {
            // 没有找到session，认为会话过期，重新登录
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "登录过期, 请重新登录!", conn);
            return;
        }

        // 3. 走到这，则说明用户是登录状态的，则从数据库中获取用户信息
        int uid = sp->get_uid();
        Json::Value user_info; // 构建用户结构体对象
        ret = _ut.select_by_userid(uid, user_info);

        if (ret == false)
        {
            // 获取用户信息失败，返回错误，找不到用户信息(算服务器错误吧)
            respHttp_JsonFormat(false, websocketpp::http::status_code::bad_request, "找不到用户信息, 请重新登录!", conn);
            return;
        }

        // 4. 走到这，说明用户信息获取成功，将用户信息序列化后发送给客户端
        std::string resp_body;
        json_util::serialize(user_info, resp_body);
        conn->append_header("Content-Type", "application/json");
        conn->set_body(resp_body);
        conn->set_status(websocketpp::http::status_code::ok);

        // 5. 刷新sessio会话的过期时间，因为客户端和服务器交互了一次
        _sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);
    }

    // 封装了统一的http响应(统一一些状态码，Content-Type字段)的函数，如果对http进行响应的话，则直接调用该接口即可。
    void respHttp_JsonFormat(bool result, websocketpp::http::status_code::value code, const std::string &reason, wsserver_t::connection_ptr &conn)
    {
        Json::Value resp_json;
        resp_json["result"] = result;
        resp_json["reason"] = reason;

        std::string resp_body;
        json_util::serialize(resp_json, resp_body);

        conn->set_status(code);
        conn->append_header("Content-Type", "application/json"); // 返回的响应类型是json格式字符串
        conn->set_body(resp_body);

        return;
    }

    // http请求的回调函数，相当于汇总函数，后续根据不同请求调用上面不同的处理函数
    void http_callback(websocketpp::connection_hdl hdl)
    {
        // 客户端的http请求
        // 1. 静态页面请求
        // 2. 动态功能请求

        // 获取客户的connection智能指针
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);

        // 获取http请求信息
        websocketpp::http::parser::request req = conn->get_request();

        std::string method = req.get_method();
        std::string uri = req.get_uri();

        // 根据method和uri处理不同的请求
        if (method == "POST" && uri == "/reg")
        {
            // 注册
            register_handler(conn);
        }
        else if (method == "POST" && uri == "/login")
        {
            // 登录
            login_handler(conn);
        }
        else if (method == "GET" && uri == "/userinfo")
        {
            // 获取用户自己的前端页面信息
            info_handler(conn);
        }
        else
        {
            // 连接网站成功
            static_handler(conn);
        }
        return;
    }

    // 验证session(cookie)
    bool get_val_from_cookie_by_key(const std::string &cookie_str, const std::string &key, std::string &val)
    {
        // 解析http中cookie字段的函数，通过调用最早之前我们实现的string_util::split函数来实现的
        // 先截取出来包含Cookie的字段，然后在该字段里面截取出来SSID的内容，这个内容就是服务器需要的会话id
        // 服务器为客户端提供任何服务器之前，都会进行会话验证，判断该用户是否已经登录成功
        // 只有登录成功的用户，服务器才会为其提供服务! (服务器总不能一股脑为所有用户都提供服务吧，这样绝对是不合理的!)

        // http请求头部中的Cookie格式：
        // Cookie：name1=value1; name2=value2; name3=value3
        // 1. 以"; "作为分隔符，分割出单个cookie信息
        std::vector<std::string> cookies;
        DLOG("cookie_str: %s", cookie_str.c_str());
        string_util::split(cookie_str, "; ", cookies);

        // 2. 对单个cookie信息分离出name和value
        for (auto &str : cookies)
        {
            std::vector<std::string> cookie;
            string_util::split(str, "=", cookie);

            if (cookie.size() != 2)
                continue;

            // 3. 如果和传入的key相同，则将value赋值给输出型参数val即可
            if (cookie.front() == key)
            {
                val = cookie.back();
                DLOG("val: %s", val.c_str());
                return true;
            }
        }
        return false;
    }

    // 下面封装一个通过cookie信息获取session详细信息的接口，后面会经常用到
    void respSend_JsonFormat(Json::Value resp, wsserver_t::connection_ptr conn)
    {
        std::string body;
        json_util::serialize(resp, body);
        conn->send(body);
        return;
    }

    session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn)
    {
        Json::Value err_resp;

        // 1. 判断用户是否登录成功(请求报头中是否存在Cookie字段)
        std::string cookie_str = conn->get_request_header("Cookie"); // 根据指定头部字段，拿到对应的字段值
        if (cookie_str.empty())
        {
            // 没有cookie，直接返回错误响应
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "没有找到cookie信息, 请重新登录!";
            respSend_JsonFormat(err_resp, conn);
            return session_ptr();
        }

        // 2. 如果有cookie信息，则查找是否存在ssid值(我们的cookie中是 SSID=val 构成的)
        std::string ssid_str;
        bool ret = get_val_from_cookie_by_key(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            // 没有ssid，直接返回错误响应
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "没有找到ssid信息, 请重新登录!";
            respSend_JsonFormat(err_resp, conn);
            return session_ptr();
        }

        // 3. 走到这，说明用户携带ssid信息，在session管理模块中查找对应的会话信息
        session_ptr sp = _sm.get_session_by_ssid(std::stoi(ssid_str));
        if (sp.get() == nullptr)
        {
            // 没有找到会话，说明会话过期，用户需重新登录
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "没有找到session信息, 请重新登录!";
            respSend_JsonFormat(err_resp, conn);
            return session_ptr();
        }

        return sp; // 会话存在且没有过期
    }

    // 游戏大厅长连接建立成功
    void wsopen_game_hall(wsserver_t::connection_ptr conn)
    {
        // 由上面的login_handler可知，登录成功时我们已经会给用户创建会话，构建的响应也已经携带了Set-Cookie字段
        // 所以用户下一次请求时一定是要携带cookie信息的
        // 1. 获取用户的cookie信息字段，通过cookie找到session
        Json::Value resp_json;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        // 2. 判断用户是否处于重复登录状态
        if (_om.is_in_game_hall(sp->get_uid()) || _om.is_in_game_room(sp->get_uid()))
        {
            resp_json["optype"] = "hall_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "玩家重复登录!";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        // 3. 将用户添加至游戏大厅的哈希表中
        _om.enter_game_hall(sp->get_uid(), conn);

        // 4. 给客户端构建响应(游戏大厅长连接建立成功)
        resp_json["optype"] = "hall_ready";
        resp_json["result"] = true;
        respSend_JsonFormat(resp_json, conn);

        // 5. 不要忘了设置会话永久存在
        _sm.set_session_expire_time(sp->ssid(), SESSION_PERMANENT);

        return;
    }

    // 游戏房间长连接建立成功
    void wsopen_game_room(wsserver_t::connection_ptr conn)
    {
        // 1. 获取用户的cookie信息字段，通过cookie找到session
        Json::Value resp_json;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        DLOG("会话验证成功: %d", sp->ssid());
        // 2. 判断用户是否已经在线(例如在其他游戏房间或者游戏大厅)
        // 如果在，则说明用户进行了二次页面获取，我们不允许一个用户已经在一个房间中还去请求别的房间
        if (_om.is_in_game_hall(sp->get_uid()) || _om.is_in_game_room(sp->get_uid()))
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "玩家重复登录!";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        DLOG("玩家没有重复登录!");
        // 3. 由于匹配成功时，我们已经为用户创建好了房间，如果房间不存在，则构建失败响应返回
        room_ptr rp = _rm.get_room_by_uid(sp->get_uid());
        if (rp.get() == nullptr)
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "没有找到玩家的房间信息!";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        DLOG("玩家的房间号信息: %d", rp->get_room_id());
        // 4. 将当前用户添加至游戏房间在线用户管理模块
        _om.enter_game_room(sp->get_uid(), conn);

        // 5. 将session设置为永久有效
        _sm.set_session_expire_time(sp->ssid(), SESSION_PERMANENT);

        // 6. 给客户端返回游戏房间准备完毕的json响应
        resp_json["optype"] = "room_ready";
        resp_json["result"] = true;
        resp_json["room_id"] = rp->get_room_id();
        resp_json["self_id"] = sp->get_uid();
        resp_json["black_id"] = rp->get_black_user();
        resp_json["white_id"] = rp->get_white_user();
        DLOG("room_id: %d", rp->get_room_id());
        DLOG("self_id: %d", sp->get_uid());
        DLOG("black_id: %d", rp->get_black_user());
        DLOG("white_id: %d", rp->get_white_user());
        respSend_JsonFormat(resp_json, conn);
        return;
    }

    // websocket长连接建立成功的回调函数，专门处理websocket连接，再判断是大厅还是游戏房间的长连接分别调用上面两个不同的处理函数
    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        // 根据uri资源路径的不同区分长连接是hall还是room的
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);

        // 获取http的请求信息
        websocketpp::http::parser::request req = conn->get_request();

        // 获取req中的uri
        std::string uri = req.get_uri();

        if (uri == "/hall")
        {
            // 建立游戏大厅的长连接
            wsopen_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 建立游戏房间的长连接
            DLOG("/room连接请求到来!");
            wsopen_game_room(conn);
        }
        return;
    }

    // 游戏大厅长连接断开的处理
    void wsclose_game_hall(wsserver_t::connection_ptr conn)
    {
        // 1. 获取客户端的session信息
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        // 2. 将玩家从游戏大厅移除
        _om.exit_game_hall(sp->get_uid());

        // 3. 将session从永久存在设为定时销毁
        _sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);
    }

    // 游戏房间长连接断开的处理
    void wsclose_game_room(wsserver_t::connection_ptr conn)
    {
        // 1. 获取客户端的session信息
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        // 2. 将玩家从游戏房间在线管理模块中移除
        _om.exit_game_room(sp->get_uid());

        // 3. 将session设置为定时销毁
        _sm.set_session_expire_time(sp->ssid(), SESSION_TIMEOUT);

        // 4. 将玩家从房间中移除，房间中所有用户都退出了，则会自动销毁房间
        _rm.remove_room_user(sp->get_uid());
    }

    // websocket连接断开的回调函数，判断是大厅还是游戏房间的长连接断开调用上面两个不同的处理函数
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
        // 根据uri资源路径的不同区分断开的长连接是hall还是room的
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);

        // 获取http的请求信息
        websocketpp::http::parser::request req = conn->get_request();

        // 获取req中的uri
        std::string uri = req.get_uri();

        if (uri == "/hall")
        {
            // 建立游戏大厅的长连接
            wsclose_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 建立游戏房间的长连接
            wsclose_game_room(conn);
        }
        return;
    }

    // 处理游戏大厅的websocket消息
    void wsmessage_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msgptr)
    {
        Json::Value resp_json;
        // 游戏大厅只有两种websocket请求
        // a. 匹配
        // b. 取消匹配

        // 1. 获取客户端的session信息，识别客户端
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        // 2. 获取请求信息，判断业务处理类型
        std::string req_body = msgptr->get_payload();
        Json::Value req_json;
        bool ret = json_util::deserialize(req_body, req_json);
        if (ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求信息解析失败";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        // 3. 对游戏大厅中的业务请求进行处理，业务请求只有两种：开始匹配 vs 取消匹配
        if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
        {
            // 3.1 开始匹配，通过匹配队列模块，将用户添加到匹配队列中
            _mm.add(sp->get_uid());
            resp_json["optype"] = "match_start";
            resp_json["result"] = true;
            respSend_JsonFormat(resp_json, conn);
            return;
        }
        else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
        {
            // 3.1 停止匹配，通过匹配队列模块，将用户从匹配队列中移除
            _mm.del(sp->get_uid());
            resp_json["optype"] = "match_stop";
            resp_json["result"] = true;
            respSend_JsonFormat(resp_json, conn);
            return;
        }
        else
        {
            resp_json["optype"] = "unknown";
            resp_json["result"] = false;
            resp_json["reason"] = "请求类型未知!";
            respSend_JsonFormat(resp_json, conn);
            return;
        }
    }

    // 处理游戏房间的websocket消息
    void wsmessage_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msgptr)
    {
        Json::Value resp_json;

        // 1. 获取客户端的session信息，识别客户端
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
            return;

        // 2. 获取客户端房间信息
        room_ptr rp = _rm.get_room_by_uid(sp->get_uid());
        if (rp.get() == nullptr)
        {
            resp_json["optype"] = "unknown";
            resp_json["result"] = false;
            resp_json["reason"] = "没有找到玩家房间信息!";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        // 3. 对玩家请求进行反序列化
        std::string req_body = msgptr->get_payload();
        Json::Value req_json;
        bool ret = json_util::deserialize(req_body, req_json);
        if (ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求信息解析失败";
            respSend_JsonFormat(resp_json, conn);
            return;
        }

        // 4. 通过游戏房间管理模块，对req_json进行后端业务处理
        // 我们之前实现房间管理类时已经对两种请求(下棋/聊天)封装处理了，直接调用handler_request就行了
        rp->handler_request(req_json);
        return;
    }

    // wsmessage_game_hall和wsmessage_game_room是在wsmessage_callback内部进行调用的
    // 也就是客户端请求的所有的websocket消息都会被发送到这个接口进行处理
    void wsmessage_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msgptr)
    {
        // 根据uri资源路径的不同区分长连接是hall还是room的
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);

        // 获取http的请求信息
        websocketpp::http::parser::request req = conn->get_request();

        // 获取req中的uri
        std::string uri = req.get_uri();

        if (uri == "/hall")
        {
            // 建立游戏大厅的长连接
            wsmessage_game_hall(conn, msgptr);
        }
        else if (uri == "/room")
        {
            // 建立游戏房间的长连接
            wsmessage_game_room(conn, msgptr);
        }
        return;
    }

private:
    // web根目录，客户端向服务器发送静态资源文件的请求(注册页面，登录页面等等)
    std::string _web_root;
    user_table _ut;
    online_manager _om;
    room_manager _rm;
    match_manager _mm;
    wsserver_t _wssvr; // websocket/http服务器，有默认构造函数，不用初始化(库中有无参构造websocket服务器对象)
    session_manager _sm;

    // 补充：
    // 1. 成员变量在类中的声明次序就是初始化顺序，与其在初始化列表的先后次序无关
    // 2. 初始化顺序：_web_root -> _ut -> _om -> _rm -> _mm -> _wssvr -> _sm
    // 3. _ut需要外部传递主机IP、端口等等信息，_om有默认构造，_rm依赖_ut和_om进行构造，_mm依赖_rm和_ut和_om进行构造，_wssvr有默认构造，_sm依赖_wssvr进行初始化
};